| ... | ... | |
|---|
| 35 | 35 | == 구성 요소 == |
|---|
| 36 | 36 | |
|---|
| 37 | 37 | === 상태 정의 - enum === |
|---|
| 38 | | {{{#!syntax solidity |
|---|
| 38 | {{{ |
|---|
| 39 | 39 | enum Stage { |
|---|
| 40 | 40 | Created, |
|---|
| 41 | 41 | Bidding, |
|---|
| ... | ... | |
|---|
| 48 | 48 | 내부적으로 {{{uint8}}}로 저장된다. 정의되지 않은 값으로의 캐스팅은 revert된다. (Solidity 0.8+)[*4 Solidity Documentation. [[https://docs.soliditylang.org/en/latest/types.html#enums|Enums]]. docs.soliditylang.org.] |
|---|
| 49 | 49 | |
|---|
| 50 | 50 | === 상태 검증 - modifier === |
|---|
| 51 | | {{{#!syntax solidity |
|---|
| 51 | {{{ |
|---|
| 52 | 52 | modifier atStage(Stage expected) { |
|---|
| 53 | 53 | require(currentStage == expected, "Invalid stage"); |
|---|
| 54 | 54 | _; |
|---|
| ... | ... | |
|---|
| 59 | 59 | |
|---|
| 60 | 60 | === 전이 함수 === |
|---|
| 61 | 61 | '''선형 전이''' - 순서대로만 진행: |
|---|
| 62 | | {{{#!syntax solidity |
|---|
| 62 | {{{ |
|---|
| 63 | 63 | function nextStage() internal { |
|---|
| 64 | 64 | currentStage = Stage(uint(currentStage) + 1); |
|---|
| 65 | 65 | } |
|---|
| 66 | 66 | }}} |
|---|
| 67 | 67 | |
|---|
| 68 | 68 | '''명시적 전이''' - 허용된 경로만: |
|---|
| 69 | | {{{#!syntax solidity |
|---|
| 69 | {{{ |
|---|
| 70 | 70 | function transitionTo(Stage next) internal { |
|---|
| 71 | 71 | require(allowedTransitions[currentStage][next]); |
|---|
| 72 | 72 | currentStage = next; |
|---|
| ... | ... | |
|---|
| 76 | 76 | === 시간 기반 전이 - timedTransition === |
|---|
| 77 | 77 | 블록체인에는 cron이 없으므로 다음 트랜잭션 호출 시 시간 경과를 검사한다.[*5 Solidity Documentation. [[https://docs.soliditylang.org/en/latest/common-patterns.html#state-machine|Common Patterns: State Machine]]. docs.soliditylang.org.] |
|---|
| 78 | 78 | |
|---|
| 79 | | {{{#!syntax solidity |
|---|
| 79 | {{{ |
|---|
| 80 | 80 | modifier timedTransition() { |
|---|
| 81 | 81 | if (currentStage == Stage.Bidding |
|---|
| 82 | 82 | && block.timestamp >= createdAt + BIDDING_DURATION) |
|---|
| ... | ... | |
|---|
| 93 | 93 | }}} |
|---|
| 94 | 94 | |
|---|
| 95 | 95 | === 이벤트 === |
|---|
| 96 | | {{{#!syntax solidity |
|---|
| 96 | {{{ |
|---|
| 97 | 97 | event StageChanged(Stage indexed from, Stage indexed to, address indexed triggeredBy); |
|---|
| 98 | 98 | }}} |
|---|
| 99 | 99 | |
|---|
| ... | ... | |
|---|
| 109 | 109 | === 비선형 전이 - 전이 테이블 === |
|---|
| 110 | 110 | 분기·복귀가 있는 워크플로우에서 사용한다. 허용된 전이를 mapping으로 정의한다.[*1] |
|---|
| 111 | 111 | |
|---|
| 112 | | {{{#!syntax solidity |
|---|
| 112 | {{{ |
|---|
| 113 | 113 | mapping(Stage => mapping(Stage => bool)) private allowedTransitions; |
|---|
| 114 | 114 | |
|---|
| 115 | 115 | constructor() { |
|---|
| ... | ... | |
|---|
| 124 | 124 | === 비트마스크 최적화 === |
|---|
| 125 | 125 | 이중 mapping은 검증당 SLOAD 2회가 필요하다. 비트마스크를 사용하면 SLOAD 1회 + 비트 연산으로 줄일 수 있다.[*6 Ethereum Yellow Paper. [[https://ethereum.github.io/yellowpaper/paper.pdf|Ethereum: A Secure Decentralised Generalised Transaction Ledger]]. Appendix G. Fee Schedule.] |
|---|
| 126 | 126 | |
|---|
| 127 | | {{{#!syntax solidity |
|---|
| 127 | {{{ |
|---|
| 128 | 128 | mapping(Stage => uint8) private transitionMap; |
|---|
| 129 | 129 | |
|---|
| 130 | 130 | // AwaitingPayment -> Funded(bit 1) 허용 |
|---|
| ... | ... | |
|---|
| 168 | 168 | * [[Checks-Effects-Interactions 패턴]] - 상태 변경 후 외부 호출. State Machine의 전이 보안에 필수적이다. |
|---|
| 169 | 169 | * [[Pull over Push 패턴]] - {{{withdraw()}}} 패턴. 상태 전이와 자금 인출을 분리하여 안전성을 높인다. |
|---|
| 170 | 170 | |
|---|
| 171 | |
|---|
| 171 | 172 | == 관련 문서 == |
|---|
| 172 | 173 | * [[블록체인]] |
|---|
| 173 | 174 | * [[스마트 컨트랙트]] |
|---|
| 174 | 175 | * [[Solidity]] |
|---|
| 175 | 176 | * [[이더리움]] |
|---|
| 176 | 177 | * [[유한 상태 기계]] |
|---|
| 177 | 178 | * [[디자인 패턴]] |
|---|